<?php
/**
 * GeryEngine
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this release in the file LICENSE
 *
 * @copyright  Copyright (c) 2007 Arjen Brouwer
 * @license    New BSD License
 * @author     Arjen Brouwer
 */

GeryEngine::loadClasses();
		
class GeryEngine {
	
	/**
	 * @var boolean $classesLoaded
	 */
	private static $classesLoaded = false;
	
	/**
	 * @var boolean
	 */
	private static $runtimeClassesLoaded = false;
	
	/**
	 * @var GeryCompileEngine $compiler
	 */
	private $compiler;
	
	/**
	 * @var Properties $overridingProperties
	 */
	private $overridingProperties = null;
	
	/**
	 * @var Properties $configuration
	 */
	private $configuration;
	
	/**
	 * @var ResourceManager $resourceManager
	 */
	private $resourceManager;
	
	/**
	 * @var Logger $logger
	 */
	private $logger;
	
	/**
	 * @var CacheManager
	 */
	private $cacheManager;
	
	
	/**
	 * @var GeryType[] Variable types
	 */
	private $types;
	
	public function __construct() {
		
		self::loadRuntimeClasses();
		
		$this->configuration = new Properties();
		$this->logger = new Logger();
	}
	
	/**
	 * Get the version of this Gery Engine
	 *
	 * @return string
	 */
	public static function getVersion() {
		static $version = null;
		
		if ($version === null)
			$version = trim(file_get_contents(dirname(__FILE__) . '/VERSION'));
			
		return $version;
	}
	
	/**
	 * Initialize the gery engine
	 * 
	 * You can pass this method as Properties object or string to configure
	 * the engine. If you pass a string it will be interpreted as a filename
	 * and a Properties instance will try to read the properties from that file.
	 *
	 * @param Properties $properties
	 */
	public function init($properties = null) 
	{
		$this->info("Running Gery Engine v" . self::getVersion());
          
		// set the overriding properties, if given  
		if (is_null($properties)) 
		{
			// do nothing
		}
		else if ($properties instanceof Properties) 
		{
			$this->overridingProperties = $properties;
		} 
		else
		{
			$file = (string)$properties;
			
			$this->overridingProperties = new Properties();
			$this->overridingProperties->loadProperties($file);	
		}
		
		$this->initializeProperties();
		$this->initializeResourceManager();
		$this->initializeCacheManager();
		$this->initializeTypeSystem();
	}
	
	private function initializeProperties() 
	{
		$this->info("Initializing properties");
		/*
		 * Always lay down the default properties first as
		 * to provide a solid base.
		 */
		if (! $this->configuration->isInitialized())
        {
            $this->setDefaultProperties();
        }
        
        /*
         * Combine the user specified properties with the configuration
         */
        if ($this->overridingProperties != null)
        {
            $this->configuration->combine($this->overridingProperties);
        }
	}
	
	private function initializeResourceManager() 
	{
		$this->info("Initializing resource manager");
		$this->resourceManager = new ResourceManager();
		$this->resourceManager->initialize($this);
	}
	
	private function initializeCacheManager() 
	{
		$this->info("Initializing cache manager");
		$this->cacheManager = new CacheManager();
		$this->cacheManager->initialize($this);
	}
	
	private function initializeTypeSystem() 
	{
		$this->info("Initializing types");

		$this->types = new HashMap();
		$this->types->put('string', 	new GeryString());
		$this->types->put('integer', 	new GeryInteger());
		$this->types->put('array', 		new GeryArray());
		$this->types->put('float', 		new GeryFloat());
		$this->types->put('boolean', 	new GeryBoolean());
	}
	
	public function setDefaultProperties() 
	{
		$this->info("Default Properties File: " . 'defaults/gery.ini');
        $this->configuration->loadProperties(dirname(__FILE__) . '/defaults/gery.ini');
    }
	
	public function setProperty($key, $value) 
	{
        if ($this->overridingProperties == null) {
            $this->overridingProperties = new Properties();
        }            
            
         $this->overridingProperties->setProperty($key, $value);
    }
    
    public function setConfiguration(Properties $configuration) 
    {
        if ($this->overridingProperties = null)
        {
            $overridingProperties = $configuration;
        }
        else
        {
        	if ($this->overridingProperties != $configuration)
        	{
                $this->overridingProperties->combine($configuration);
            }
        }
    } 
    
    /**
     * Get the configuration of this engine
     * 
     * @return Properties
     */
    public function getConfiguration() 
    {
    	return $this->configuration;
    }
    
    /**
     * Get a template instance
     *
     * @param String $resourceName The name of the resource
     * @return TemplateBase
     */
	public function getTemplate($resourceName) 
	{
		// the class cache
		static $classCache = array();
		
		// get template from class cache
		if (key_exists($resourceName, $classCache))
			return new $classCache[$resourceName]($this);
		
		// fetch the resource from the resource loader
		$resource = $this->resourceManager->getResource($resourceName);
		
		// try to fetch the template from cache
		$cachedTemplate = $this->getCachedTemplate($resource);
		
		// return the template
		if ($cachedTemplate)
			return $cachedTemplate;
		
		$compiledTemplate = $this->compileTemplate($resource);
		
		// if the resource is cachable
		// send the compiled template to the cache manager
		if ($resource->isCachable())
			$this->cacheManager->cacheTemplate($resource, $compiledTemplate);
		
		// get an instance of the compiled template
		$instance = $compiledTemplate->getInstance($this);
		
		// cache the template
		$classCache[$resourceName] = $instance;
		
		// return the template instance
		return $instance;
	}
	
	private function getCachedTemplate($resource) 
	{
		if ($resource->isCachable()) {
			
			// get the name of the cached compiled template
			$cachedTemplate = $this->cacheManager->getCachedTemplate($resource);
			
			// if a cached template is found
			if ($cachedTemplate) {
				
				$instance = $cachedTemplate->getInstance($this);
				
				if ($instance->getLastModified() == $resource->getLastModified()) 
				{
					// cache the instance in the template cache
					$classCache[$resource->getName()] = $instance;
					
					return $instance;
				}
			}
		}
		
		return null;
	}
	
	public function getResource($resourceName) 
	{
		return $this->resourceManager->getResource($resourceName);
	}
	
	/**
	 * Compile a resource into a template
	 *
	 * @param Resource $resource
	 * @return CompiledTemplate
	 */
	private function compileTemplate($resource) 
	{
		
		// create a the compiler if necesary
		if ($this->compiler == null)
			$this->compiler = new GeryCompileEngine($this->logger);
		
		// compile template
		$this->info("GeryEngine: Start compiling template");
		$compiledTemplate = $this->compiler->compile($resource);
		
		$this->info("GeryEngine: Template compiled; className = " . $compiledTemplate->getClassName());
		
		return $compiledTemplate;
	}
	
	/**
	 * Get a variable type manager
	 * 
	 * @return GeryType
	 */
	public function getType($typeName) 
	{
		return $this->types->get($typeName);
	}
	
	/**
	 * Get the logger
	 *
	 * @return Logger
	 */
	function getLogger() {
		return $this->logger;
	}
	
	function fatal($msg) {
		$this->logger->fatal($msg);
	}
	
	function critical($msg) {
		$this->logger->critical($msg);
	}
	
	function error($msg) {
		$this->logger->error($msg);
	}
	
	function warning($msg) {
		$this->logger->warning($msg);
	}
	
	function info($msg) {
		$this->logger->info($msg);
	}
	
	function debug($msg) {
		$this->logger->debug($msg);
	}
	
	public static function loadClasses() {
		
		// check if classes were already loaded
		if (self::$classesLoaded)
			return;
			
		self::$classesLoaded = true;
	
		$basePath = dirname(__FILE__);
		
		require_once($basePath . '/lib/util/Properties.php');
		require_once($basePath . '/lib/util/Logger.php');
		require_once($basePath . '/lib/util/HashMap.php');
		
		require_once($basePath . '/lib/context/IGeryContext.php');
		require_once($basePath . '/lib/context/AbstractGeryContext.php');
		require_once($basePath . '/lib/context/GeryContext.php');
		
		require_once($basePath . '/lib/types/IGeryArray.php');
	}
	
	private static function loadRuntimeClasses() {
		
		// check if classes were already loaded
		if (self::$runtimeClassesLoaded)
			return;
			
		
		self::$runtimeClassesLoaded = true;
		
		$basePath = dirname(__FILE__);
		
		// load template classes		
		require_once($basePath . '/lib/util/FileUtil.php');
		require_once($basePath . '/lib/util/ReferenceProxy.php');
		require_once($basePath . '/lib/util/MethodProxy.php');
		
		require_once($basePath . '/lib/cache/CacheManager.php');
		require_once($basePath . '/lib/cache/CachedTemplate.php');
		
		require_once($basePath . '/lib/resource/Resource.php');
		require_once($basePath . '/lib/resource/ResourceManager.php');
		require_once($basePath . '/lib/resource/ResourceLoaderFactory.php');
		require_once($basePath . '/lib/resource/loader/ResourceLoader.php');
		require_once($basePath . '/lib/resource/loader/FileResourceLoader.php');
		
		require_once($basePath . '/lib/RuntimeServices.php');
		
		require_once($basePath . '/lib/template/TemplateBase.php');
		require_once($basePath . '/lib/template/TemplateMath.php');
		require_once($basePath . '/lib/template/TemplateReferenceCompiler.php');
		require_once($basePath . '/lib/template/CompiledTemplate.php');
		require_once($basePath . '/lib/template/TemplateLocation.php');
		require_once($basePath . '/lib/template/TemplateError.php');
		
		require_once($basePath . '/lib/context/GeryProxyContext.php');
		require_once($basePath . '/lib/context/GeryTemplateContext.php');
		require_once($basePath . '/lib/context/GeryMacroContext.php');
		
		require_once($basePath . '/lib/exception/GeryException.php');	
		require_once($basePath . '/lib/exception/GeryParseErrorException.php');	
		require_once($basePath . '/lib/exception/GeryResourceNotFoundException.php');
		require_once($basePath . '/lib/exception/GeryRuntimeException.php');
		require_once($basePath . '/lib/exception/GeryInitializationException.php');

		require_once($basePath . '/lib/types/GeryType.php');
		require_once($basePath . '/lib/types/GeryBaseType.php');
		require_once($basePath . '/lib/types/GeryBoolean.php');
		require_once($basePath . '/lib/types/GeryFloat.php');
		require_once($basePath . '/lib/types/GeryInteger.php');
		require_once($basePath . '/lib/types/GeryString.php');
		require_once($basePath . '/lib/types/GeryArray.php');
		
		require_once($basePath . '/lib/macro/GeryMacroNamer.php');
		
		require_once($basePath . '/GeryCompileEngine.php');
	}
}
?>